//
// Created by jaesonzhang on 2024/9/13.
//

#include "CanvasJSBinding.h"
#include "Canvas.h"
#include "Context.h"
#include "../quickjs/quickjs.h"
#include "../engine/JSApp.h"

#define countof(x) (sizeof(x) / sizeof((x)[0]))

JSClassID js_canvas_class_id;

static void js_canvas_finalizer(JSRuntime *rt, JSValue val) {
  auto *s =
      static_cast<skiacanvas::Canvas *>(JS_GetOpaque(val, js_canvas_class_id));
  s->~Canvas();
  js_free_rt(rt, s);
  printf("delete,canvas %d\n", s);
}

static JSValue js_canvas_ctor(JSContext *ctx,
                              JSValueConst new_target,
                              int argc, JSValueConst *argv) {
  JSValue obj = JS_UNDEFINED;
  JSValue proto;
  int32_t canvasId = 0;
  if (JS_ToInt32(ctx, &canvasId, argv[0])) {
    return JS_EXCEPTION;
  }
  JSApp *jsApp = static_cast<JSApp *>(JS_GetContextOpaque(ctx));
  if (jsApp == nullptr) {
    return JS_EXCEPTION;
  }

  /* using new_target to get the prototype is necessary when the
     class is extended. */
  proto = JS_GetPropertyStr(ctx, new_target, "prototype");
  if (JS_IsException(proto))
    return JS_EXCEPTION;
  obj = JS_NewObjectProtoClass(ctx, proto, js_canvas_class_id);
  JS_FreeValue(ctx, proto);
  if (JS_IsException(obj)) {
    JS_FreeValue(ctx, obj);
    return JS_EXCEPTION;
  }


  auto w = JS_NewUint32(ctx, jsApp->getWidth());
  auto h = JS_NewUint32(ctx, jsApp->getHeight());
  JSValue args[2] = {w, h};
  // 在 Canvas 的构造函数中创建 Context 的实例
  auto ctxProto = JS_GetPropertyStr(ctx,
                                    JS_GetGlobalObject(ctx),
                                    "Context");
  JSValue objCtx = JS_CallConstructor(ctx, ctxProto, 2, args);
  if (JS_IsException(objCtx)) {
    JS_FreeValue(ctx, obj);
    JS_FreeValue(ctx, objCtx);
    return JS_EXCEPTION;
  }
  // 将 objCtx 作为属性添加到 obj 中
  JS_SetPropertyStr(ctx, obj, "ctx", objCtx);

  extern JSClassID js_context_class_id;
  void *buffer = js_malloc(ctx, sizeof(skiacanvas::Canvas));
  auto x = JS_GetOpaque2(ctx, objCtx, js_context_class_id);
  auto canvas = new(buffer)skiacanvas::Canvas(jsApp,
                                              static_cast<skiacanvas::Context *>(x),
                                              canvasId,
                                              jsApp->getWidth(),
                                              jsApp->getHeight());

  JS_SetOpaque(obj, buffer);
  return obj;
}

static JSValue js_canvas_get_context(JSContext *ctx, JSValueConst this_val,
                                     int argc, JSValueConst *argv) {
  skiacanvas::Canvas *s = static_cast<skiacanvas::Canvas *>(JS_GetOpaque2(ctx,
                                                                          this_val,
                                                                          js_canvas_class_id));
  if (!s)
    return JS_EXCEPTION;

  JSAtom ctxProp = JS_NewAtom(ctx, "ctx");
  return JS_GetProperty(ctx, this_val, ctxProp);
}

static JSValue js_canvas_commit(JSContext *ctx, JSValueConst this_val,
                                int argc, JSValueConst *argv) {
  skiacanvas::Canvas
      *pCanvas = static_cast<skiacanvas::Canvas *>(JS_GetOpaque2(ctx,
                                                                 this_val,
                                                                 js_canvas_class_id));
  if (!pCanvas)
    return JS_EXCEPTION;

  pCanvas->commit();
  return JS_NewBool(ctx, 1);
}

static JSValue
js_canvas_get_wh(JSContext *ctx, JSValueConst this_val, int magic) {
  skiacanvas::Canvas
      *pCanvas = static_cast<skiacanvas::Canvas *>(JS_GetOpaque2(ctx,
                                                                 this_val,
                                                                 js_canvas_class_id));
  if (!pCanvas)
    return JS_EXCEPTION;

  if (magic == 0)
    return JS_NewInt32(ctx, pCanvas->width);
  else
    return JS_NewInt32(ctx, pCanvas->height);
}

static JSClassDef js_canvas_class = {
    "Canvas",
    .finalizer = js_canvas_finalizer,
};

static const JSCFunctionListEntry js_canvas_proto_funcs[] = {
    JS_CGETSET_MAGIC_DEF("width", js_canvas_get_wh, NULL, 0),
    JS_CGETSET_MAGIC_DEF("height", js_canvas_get_wh, NULL, 1),
    JS_CFUNC_DEF("getContext", 0, js_canvas_get_context),
    JS_CFUNC_DEF("commit", 0, js_canvas_commit),
};

int js_canvas_init(JSContext *ctx) {
  JSValue canvas_proto, canvas_class;
  /* create the Canvas class */
  JS_NewClassID(&js_canvas_class_id);
  JS_NewClass(JS_GetRuntime(ctx), js_canvas_class_id, &js_canvas_class);

  canvas_proto = JS_NewObject(ctx);
  JS_SetPropertyFunctionList(ctx,
                             canvas_proto,
                             js_canvas_proto_funcs,
                             countof(js_canvas_proto_funcs));

  canvas_class =
      JS_NewCFunction2(ctx,
                       js_canvas_ctor,
                       "Canvas",
                       2,
                       JS_CFUNC_constructor,
                       0);
  /* set proto.constructor and ctor.prototype */
  JS_SetConstructor(ctx, canvas_class, canvas_proto);
  JS_SetClassProto(ctx, js_canvas_class_id, canvas_proto);

  // 设置到全局对象上
  JS_SetPropertyStr(ctx,
                    JS_GetGlobalObject(ctx),
                    "Canvas",
                    canvas_class);

  return 0;
}

